<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>IOReader类说明文档</title>
    <style>
        body {
            font-family: 'Microsoft YaHei', Arial, sans-serif;
            line-height: 1.6;
            color: #333;
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
        }
        h1 {
            color: #0066cc;
            border-bottom: 2px solid #0066cc;
            padding-bottom: 10px;
        }
        h2 {
            color: #0066cc;
            margin-top: 30px;
            border-left: 4px solid #0066cc;
            padding-left: 10px;
        }
        h3 {
            color: #333;
            margin-top: 20px;
        }
        pre {
            background-color: #f5f5f5;
            padding: 10px;
            border-radius: 5px;
            overflow-x: auto;
        }
        code {
            font-family: Consolas, Monaco, 'Andale Mono', monospace;
            background-color: #f5f5f5;
            padding: 2px 4px;
            border-radius: 3px;
        }
        table {
            border-collapse: collapse;
            width: 100%;
            margin: 20px 0;
        }
        th, td {
            border: 1px solid #ddd;
            padding: 8px 12px;
            text-align: left;
        }
        th {
            background-color: #f2f2f2;
        }
        .note {
            background-color: #fff8dc;
            border-left: 4px solid #ffeb3b;
            padding: 10px;
            margin: 10px 0;
        }
        .warning {
            background-color: #ffebee;
            border-left: 4px solid #f44336;
            padding: 10px;
            margin: 10px 0;
        }
        .method-signature {
            font-weight: bold;
            color: #0066cc;
        }
        .parameter {
            font-style: italic;
        }
        .return-value {
            color: #009688;
        }
        .example {
            background-color: #e8f5e9;
            padding: 10px;
            border-radius: 5px;
            margin: 10px 0;
        }
    </style>
</head>
<body>
    <h1>IOReader类说明文档</h1>
    
    <h2>1. 类概述</h2>
    <p>IOReader类是一个用于处理IO操作的Qt类，主要用于通过Modbus协议与串口继电器进行通信，实现数字输入(DI)和数字输出(DO)状态的读取和控制。该类继承自QObject，支持Qt的信号与槽机制。</p>
    
    <div class="note">
        <p><strong>注意：</strong>该类于2022-1-17添加，用于替代或补充原有的IO操作功能。</p>
    </div>
    
    <h2>2. 类的主要功能</h2>
    <ul>
        <li>通过串口与继电器设备建立通信连接</li>
        <li>读取设备的数字输入(DI)状态</li>
        <li>读取设备的数字输出(DO)状态</li>
        <li>控制设备的数字输出(DO)状态</li>
        <li>处理Modbus协议的数据帧构建和解析</li>
        <li>错误处理和日志记录</li>
    </ul>
    
    <h2>3. 类的成员变量</h2>
    <table>
        <tr>
            <th>变量名</th>
            <th>类型</th>
            <th>说明</th>
        </tr>
        <tr>
            <td>m_strRtuName</td>
            <td>QString</td>
            <td>RTU设备名称，通常是串口名</td>
        </tr>
        <tr>
            <td>m_pRtuClient</td>
            <td>CXnSerial*</td>
            <td>串口通信客户端对象指针</td>
        </tr>
        <tr>
            <td>m_bRtuOpened</td>
            <td>bool</td>
            <td>RTU设备是否已打开的标志</td>
        </tr>
        <tr>
            <td>m_iDevIndex</td>
            <td>int</td>
            <td>设备索引号</td>
        </tr>
        <tr>
            <td>m_pDataBuf</td>
            <td>char[500]</td>
            <td>数据缓冲区，用于存储通信数据</td>
        </tr>
        <tr>
            <td>m_readData</td>
            <td>char[250]</td>
            <td>读取数据缓冲区</td>
        </tr>
        <tr>
            <td>m_dryCmdList</td>
            <td>QList<SPcsSetCmd></td>
            <td>命令列表，存储待执行的命令</td>
        </tr>
        <tr>
            <td>m_mutex</td>
            <td>QMutex</td>
            <td>互斥锁，用于线程同步</td>
        </tr>
    </table>
    
    <h2>4. 类的主要方法</h2>
    
    <h3>4.1 构造和析构</h3>
    <div class="method-signature">IOReader(QObject *parent, QString strRtuName)</div>
    <p>构造函数，初始化IOReader对象。</p>
    <ul>
        <li><span class="parameter">parent</span>: 父对象指针，用于Qt对象树管理</li>
        <li><span class="parameter">strRtuName</span>: RTU设备名称，通常是串口名</li>
    </ul>
    
    <div class="method-signature">~IOReader()</div>
    <p>析构函数，释放资源。</p>
    
    <h3>4.2 串口通信</h3>
    <div class="method-signature">bool openSerialPort()</div>
    <p>打开与串口继电器通信的串口。</p>
    <p><span class="return-value">返回值</span>: 成功返回true，失败返回false</p>
    
    <div class="method-signature">int readDevReplay(quint16 step, quint16 desiredLen)</div>
    <p>读取设备回复的数据。</p>
    <ul>
        <li><span class="parameter">step</span>: 当前操作步骤，用于日志记录</li>
        <li><span class="parameter">desiredLen</span>: 期望读取的数据长度</li>
    </ul>
    <p><span class="return-value">返回值</span>: 实际读取的数据长度</p>
    
    <h3>4.3 DI/DO状态读取与控制</h3>
    <div class="method-signature">int readDevDIState(int devIndex, DryData &dryData)</div>
    <p>读取设备的数字输入(DI)状态。</p>
    <ul>
        <li><span class="parameter">devIndex</span>: 设备索引</li>
        <li><span class="parameter">dryData</span>: 干接点数据结构，用于存储读取结果</li>
    </ul>
    <p><span class="return-value">返回值</span>: 0表示成功，负值表示失败</p>
    
    <div class="method-signature">int readDevDOState(int devIndex, DryData &dryData)</div>
    <p>读取设备的数字输出(DO)状态。</p>
    <ul>
        <li><span class="parameter">devIndex</span>: 设备索引</li>
        <li><span class="parameter">dryData</span>: 干接点数据结构，用于存储读取结果</li>
    </ul>
    <p><span class="return-value">返回值</span>: 0表示成功，负值表示失败</p>
    
    <div class="method-signature">int setDevAllDoState(int devIndex, DryData &dryData)</div>
    <p>设置设备的所有数字输出(DO)状态。</p>
    <ul>
        <li><span class="parameter">devIndex</span>: 设备索引</li>
        <li><span class="parameter">dryData</span>: 干接点数据结构，包含要设置的DO状态</li>
    </ul>
    <p><span class="return-value">返回值</span>: 0表示成功，负值表示失败</p>
    
    <div class="method-signature">bool setDoStateHZ(int doIndex, quint16 usNeedHz)</div>
    <p>控制指定DO口是否闭合(用于分励脱扣控制)。</p>
    <ul>
        <li><span class="parameter">doIndex</span>: DO索引</li>
        <li><span class="parameter">usNeedHz</span>: 是否需要分励脱扣</li>
    </ul>
    <p><span class="return-value">返回值</span>: 成功返回true，失败返回false</p>
    
    <h3>4.4 数据处理</h3>
    <div class="method-signature">int analyRtuDatagram(quint16 uaDevAddr, char* pRecDataBuf, int iLen, int needByteCount)</div>
    <p>分析RTU数据报文。</p>
    <ul>
        <li><span class="parameter">uaDevAddr</span>: 设备地址</li>
        <li><span class="parameter">pRecDataBuf</span>: 接收数据缓冲区</li>
        <li><span class="parameter">iLen</span>: 数据长度</li>
        <li><span class="parameter">needByteCount</span>: 需要的字节数</li>
    </ul>
    <p><span class="return-value">返回值</span>: 0表示成功，负值表示失败</p>
    
    <div class="method-signature">int getParamFromBuffer(char* pData, quint16& uiParamVal)</div>
    <p>从缓冲区获取16位参数值。</p>
    <ul>
        <li><span class="parameter">pData</span>: 数据缓冲区</li>
        <li><span class="parameter">uiParamVal</span>: 输出参数，获取的16位值</li>
    </ul>
    <p><span class="return-value">返回值</span>: 0表示成功</p>
    
    <div class="method-signature">int getParamFromBuffer(char* pData, quint32& uiParamVal)</div>
    <p>从缓冲区获取32位参数值。</p>
    <ul>
        <li><span class="parameter">pData</span>: 数据缓冲区</li>
        <li><span class="parameter">uiParamVal</span>: 输出参数，获取的32位值</li>
    </ul>
    <p><span class="return-value">返回值</span>: 0表示成功</p>
    
    <div class="method-signature">int addParam2Buffer(char* pData, quint16 uiParamVal)</div>
    <p>将16位参数值添加到缓冲区。</p>
    <ul>
        <li><span class="parameter">pData</span>: 数据缓冲区</li>
        <li><span class="parameter">uiParamVal</span>: 要添加的16位值</li>
    </ul>
    <p><span class="return-value">返回值</span>: 0表示成功</p>
    
    <h3>4.5 辅助方法</h3>
    <div class="method-signature">void sendLog(int iLogType, const QString& strLog)</div>
    <p>发送日志信息。</p>
    <ul>
        <li><span class="parameter">iLogType</span>: 日志类型</li>
        <li><span class="parameter">strLog</span>: 日志内容</li>
    </ul>
    
    <div class="method-signature">void printSendInfo(int iIndexStep, char* pSendBufData, int iLen)</div>
    <p>打印发送的数据信息。</p>
    <ul>
        <li><span class="parameter">iIndexStep</span>: 步骤索引</li>
        <li><span class="parameter">pSendBufData</span>: 发送数据缓冲区</li>
        <li><span class="parameter">iLen</span>: 数据长度</li>
    </ul>
    
    <div class="method-signature">void printReceInfo(int iIndexStep, char* pSendBufData, int iLen)</div>
    <p>打印接收的数据信息。</p>
    <ul>
        <li><span class="parameter">iIndexStep</span>: 步骤索引</li>
        <li><span class="parameter">pSendBufData</span>: 接收数据缓冲区</li>
        <li><span class="parameter">iLen</span>: 数据长度</li>
    </ul>
    
    <h2>5. Modbus通信协议</h2>
    <p>IOReader类使用Modbus RTU协议与串口继电器通信。以下是主要的功能码：</p>
    
    <table>
        <tr>
            <th>功能码</th>
            <th>宏定义</th>
            <th>说明</th>
        </tr>
        <tr>
            <td>0x01</td>
            <td>DO_READ_CMD</td>
            <td>查询DO继电器状态指令</td>
        </tr>
        <tr>
            <td>0x02</td>
            <td>DI_READ_CMD</td>
            <td>查询DI离散量输入（光耦)状态指令</td>
        </tr>
        <tr>
            <td>0x05</td>
            <td>DO_CTRL_CMD</td>
            <td>控制DO继电器的打开或关闭</td>
        </tr>
        <tr>
            <td>0x0F</td>
            <td>DO_SET_CMD</td>
            <td>控制多个DO指令</td>
        </tr>
    </table>
    
    <h3>5.1 数据帧格式</h3>
    <p>Modbus RTU协议的数据帧格式如下：</p>
    
    <h4>查询帧格式</h4>
    <pre>设备地址(1字节) + 功能码(1字节) + 数据(N字节) + CRC校验(2字节)</pre>
    
    <h4>响应帧格式</h4>
    <pre>设备地址(1字节) + 功能码(1字节) + 数据长度(1字节) + 数据(N字节) + CRC校验(2字节)</pre>
    
    <h3>5.2 通信示例</h3>
    
    <h4>读取DI状态示例</h4>
    <div class="example">
        <p><strong>发送</strong>: FE 02 00 00 00 06 EC 07</p>
        <p><strong>接收</strong>: FE 02 01 00 91 9C</p>
        <p><strong>说明</strong>: 读取6路DI (6路都断开)</p>
    </div>
    
    <h4>读取DO状态示例</h4>
    <div class="example">
        <p><strong>发送</strong>: FE 01 00 00 00 04 29 C6</p>
        <p><strong>接收</strong>: FE 01 01 01 A0 5C</p>
        <p><strong>说明</strong>: 读取4路DO (第1路闭合,即继电器打开)</p>
    </div>
    
    <h4>设置DO状态示例</h4>
    <div class="example">
        <p><strong>发送</strong>: FE 0F 00 00 00 02 01 03 D1 92</p>
        <p><strong>接收</strong>: FE 0F 00 00 00 02 C0 05</p>
        <p><strong>说明</strong>: 控制打开DO1，DO2</p>
    </div>
    
    <h2>6. 使用示例</h2>
    
    <h3>6.1 创建IOReader对象</h3>
    <pre><code>// 创建IOReader对象，串口名为COM1
IOReader* pIOReader = new IOReader(this, "COM1");

// 打开串口
if (pIOReader->openSerialPort()) {
    qDebug() << "串口打开成功";
} else {
    qDebug() << "串口打开失败";
}</code></pre>
    
    <h3>6.2 读取DI状态</h3>
    <pre><code>// 准备DryData结构
DryData dryData;
dryData.zz404D_addr = 0xFE;      // 设备地址
dryData.diStartRegAddr = 0;      // DI起始地址
dryData.devDiCount = 6;          // DI数量

// 读取DI状态
int ret = pIOReader->readDevDIState(0, dryData);
if (ret == 0) {
    qDebug() << "DI状态:" << dryData.DIVal;
} else {
    qDebug() << "读取DI状态失败:" << ret;
}</code></pre>
    
    <h3>6.3 读取DO状态</h3>
    <pre><code>// 准备DryData结构
DryData dryData;
dryData.zz404D_addr = 0xFE;      // 设备地址
dryData.doStartRegAddr = 0;      // DO起始地址
dryData.devDoCount = 6;          // DO数量

// 读取DO状态
int ret = pIOReader->readDevDOState(0, dryData);
if (ret == 0) {
    qDebug() << "DO状态:" << dryData.IOCtrl;
} else {
    qDebug() << "读取DO状态失败:" << ret;
}</code></pre>
    
    <h3>6.4 设置DO状态</h3>
    <pre><code>// 准备DryData结构
DryData dryData;
dryData.zz404D_addr = 0xFE;      // 设备地址
dryData.doStartRegAddr = 0;      // DO起始地址
dryData.devDoCount = 6;          // DO数量

// 设置DO1和DO2为闭合状态
dryData.IOCtrl = 0x03;  // 二进制: 000011

// 设置DO状态
int ret = pIOReader->setDevAllDoState(0, dryData);
if (ret == 0) {
    qDebug() << "设置DO状态成功";
} else {
    qDebug() << "设置DO状态失败:" << ret;
}</code></pre>
    
    <h2>7. 注意事项</h2>
    <div class="warning">
        <p><strong>警告：</strong></p>
        <ol>
            <li>使用前必须确保串口设备已正确连接并配置。</li>
            <li>控制继电器时需注意安全，避免频繁切换可能导致设备损坏。</li>
            <li>继电器闭合的动作，打开继电器不能超过1秒，否则会烧坏断路器。</li>
            <li>通信超时参数(rwTimeoutSec)应根据实际情况设置，默认范围为300ms-3000ms。</li>
        </ol>
    </div>
    
    <h2>8. 更新历史</h2>
    <table>
        <tr>
            <th>日期</th>
            <th>版本</th>
            <th>更新内容</th>
        </tr>
        <tr>
            <td>2022-01-17</td>
            <td>1.0</td>
            <td>初始版本，添加IOReader类</td>
        </tr>
        <tr>
            <td>2022-03-30</td>
            <td>1.1</td>
            <td>改为解析报文成功，才认为通迅成功 (上海电气西藏项目，线接反也误认为通迅正常)</td>
        </tr>
        <tr>
            <td>2022-08-02</td>
            <td>1.2</td>
            <td>修改多设备支持逻辑</td>
        </tr>
        <tr>
            <td>2023-03-13</td>
            <td>1.3</td>
            <td>添加对超过8个DI/DO的支持</td>
        </tr>
        <tr>
            <td>2023-11-09</td>
            <td>1.4</td>
            <td>添加读取10寸屏自带的DI/DO状态功能</td>
        </tr>
        <tr>
            <td>2023-11-17</td>
            <td>1.5</td>
            <td>记录错误数据和长度</td>
        </tr>
        <tr>
            <td>2024-06-14</td>
            <td>1.6</td>
            <td>添加DO状态到fStackTemp[48]</td>
        </tr>
        <tr>
            <td>2024-09-15</td>
            <td>1.7</td>
            <td>删除fStackTemp相关代码</td>
        </tr>
    </table>
    
    <h2>9. 相关类</h2>
    <ul>
        <li>DryCtrlThread - 干接点控制线程类</li>
        <li>CXnSerial - 串口通信类</li>
        <li>KgGlobalData - 全局数据类</li>
    </ul>
    
    <div class="note">
        <p><strong>文档生成日期：</strong>2024年9月</p>
    </div>
</body>
</html>